Humble Object
The book has now been published and the content of this chapter has likely changed substanstially.Please see page 695 of xUnit Test Patterns for the latest information.
How can we make code testable when it is too closely coupled to its environment?
We extract the logic into a separate easy-to-test component that is decoupled from its environment.
Sketch Humble Object embedded from Humble Object.gif
We are often faced with trying to test software that is closely coupled to some kind of framework. Examples include visual components (e.g. widgets, dialogs, etc.) and transactional component plug-ins. Testing these objects is hard because it can be expensive or impossible to construct all the objects that our system under test (SUT) needs to interact with. In other cases we have objects that are hard to test because they run asynchronously; examples include active objects (e.g. threads, processes, web servers, etc.) and user interfaces. Testing these objects is hard because the asynchronicity introduces uncertainty, interprocess coordination and the need for delays in tests. Faced with all these issues developers often just give up on testing this kind of code. This leads to Production Bugs (page X) caused by Untested Code and Untested Requirements.
Humble Object is a way to bring the logic of these hard-to-instantiate objects under test in a cost-effective manner.
How It Works
We extract all the logic from the hard-to-test component into a component that is testable via synchronous tests. This component implements a service interface consisting of methods that expose all the logic of the untestable component; the only difference is that they are accessible via synchronous method calls. As a result, the Humble Object component becomes a very thin adapter layer that contains very little code. Each time the Humble Object is called by the framework, it delegates to the testable component. If the testable component needs any information from the context, the Humble Object is responsible for retrieving it and passing it to the testable component. The Humble Object code is typically so simple that we often don't bother writing tests for it because it can be quite difficult to set up the environment need to run it.
When To Use It
We can and should introduce a Humble Object whenever we have non-trivial logic in a component that is hard to instantiate because it depends on a framework or can only be accessed asynchronously. There are lots of reasons for objects being hard-to-test so there need to be lots of variations in how we break the dependencies. The following are the most common examples of Humble Object but we shouldn't be surprised if we need to invent our own variation.
Variation: Humble Dialog
Graphical user interface (GUI) frameworks require us to provide objects to represent our pages and controls. These objects provide logic to translate user actions into the underlying system actions and to translate the system responses back into user recognizable behavior. This logic may involve invoking the application behind the user interface and/or modifying the state of this or other visual objects.
Visual objects are very hard to test efficiently because they are tightly coupled to the presentation framework that invokes them. The test would need to simulate that environment to provide the visual object with all the information and facilities it requires. Further complicating the issue is that these frameworks often run in their own thread of control and that means we'll need to use asynchronous tests that are harder to write and result in Slow Tests (page X) and Nondeterministic Tests (see Erratic Test on page X). We can use Humble Object here to move all the controller and view updating logic out of the framework-dependent object into a testable object.
Variation: Humble Executable
Many programs contain active objects. Active objects have their own thread of execution so they can do things in parallel with other activities of the system. Examples of active objects include anything that runs in a separate process (e.g. Windows applications in .exe files) or threads (in Java, any object that implements Runnable). They may be launched directly by the client or they may be started automatically and process requests from a queue and send replies via a return message. Either way, we would have to write asynchronous tests (complete with interprocess coordination and/or explicit delays and Neverfail Tests (see Production Bugs)) to verify their behavior.
Humble Executable is a way to bring the logic of the executable under test without incurring the delays that lead to Slow Tests and Nondeterministic Tests. We extract all the logic from the executable into a component that is testable via synchronous tests. This component implements a service interface consisting of methods that expose all the logic of the executable; the only difference is that they are accessible via synchronous method calls. The testable component may be a Windows DLL, a Java JAR containing a Service Facade[CJ2EEP] class or some other language component or class that exposes the services of the executable in a testable way.
The Humble Executable component then contains very little code. All it does in its thread of control is to load the testable component (if a True Humble Object) and delegate to it. As a result, it requires only one or two tests to verify it does this correctly. These tests still take seconds to execute but they have a much smaller impact on the overall test suite execution time since there are so few of them. Since this code is not going to change very often, these tests can even be omitted from the suite of tests that developers execute before check in to speed up test suite execution times. Of course, we would still prefer to run them from the automated build process.
Variation: Humble Transaction Controller
Many applications use databases to persist their state. Fixture setup with databases can be slow and complex, and leftover fixtures can wreak havoc with subsequent tests and test runs. If we are using a Shared Fixture (page X), this can cause Erratic Tests. Humble Transaction Controller is a way to make testing of the logic that runs within the transaction easier by making it possible for the test to control the transaction. This allows us to exercise the logic, verify the outcome and then abort the transaction leaving no trace of our activity in the database.
To implement Humble Transaction Controller we use an Extract Method[Fowler] refactoring to move all the logic we want to test from the code that controls the transaction into a separate method that knows nothing about transaction control and which can be called by the test. Since the transaction control is done by the caller, the test can start, commit (if it so chooses) and (most commonly) roll back the transaction. This is one case where it is the behavior and not the dependencies that cause us to bypass the Humble Object when testing the business logic. As a result, we are more likely to be able to get away with a Poor Man's Humble Object than in many other cases.
As for the Humble Object, it contains no business logic so the only behavior that needs to be tested is whether it commits and rolls back the transaction properly based on the outcome of the methods it calls. We can write a test that replaces the testable component with a Test Stub (page X) that throws an exception and verify that this results in a roll back of the transaction. If we are using a Poor Man's Humble Object, the stub would be implemented as a Subclassed Test Double (see Test-Specific Subclass on page X) that overrides the "real" methods with ones that throw exceptions.
Many of the major application server technologies support this pattern either directly or indirectly by taking transaction control away from the business objects that we write. If we are building our software without using a transaction control framework, we may need to implement our own Humble Transaction Controller. See the "Implementation" section for some ideas on how we can do the separation.
Variation: Humble Container Adapter
Speaking of "containers", we often have to implement specific interfaces to allow our objects to run inside an application server (e.g. the "EJB session bean" interface.) Another example of Humble Object is to design our objects to be container-independent and have a Humble Container Adapter that adapts them to the interface required by container. This makes our logic components easy to test outside the container which dramatically reduces the time for an "edit-compile-test" cycle.
Implementation Notes
We came make the logic that normally runs inside the Humble Object testable in several different ways. All the ways involve exposing the logic so that it can be verified using synchronous tests. What they vary in is how the logic is exposed. Regardless of how the logic is exposed, test-driven purists would like to see tests that verify that the Humble Object is calling the extracted logic properly. This can be done by replacing the real methods with some kind of Test Double (page X) implementation.
Variation: Poor Man's Humble Object
The simplest way is to isolate and expose each piece of logic we want to verify into a separate method. We can do this by using an Extract Method refactoring on inline logic and then making the resulting method visible from the test. We have to make sure that this method doesn't require anything from the context; it is best if everything it needs to do its work is passed in as arguments but they could also be placed in fields. It can be problematic if the testable component needs to call methods to access information it needs if those methods are dependent on the (non-existent / faked) context as this makes writing the tests more complex.
This approach is the "poor man's" Humble Object and it works fine if there are no obstacles to instantiating the Humble Object (e.g. automatically starting its thread, no public constructor, unsatisfiable dependencies, etc..) Use of a Test-Specific Subclass can also help break these dependencies by providing a test-friendly constructor and exposing private methods to the test.
When testing a Subclassed Humble Object or a Poor Man's Humble Object, we can build the Test Spy (page X) as a Subclassed Test Double of the Humble Object to record when the methods in question were called. We then use assertions within the Test Method (page X) to verify that the values recorded match what we expected.
Variation: True Humble Object
At the other extreme, we can put the logic we want to test into a separate class and have the Humble Object delegate to an instance of it. This is the approach implied in the introduction above and it will work in almost any circumstance where we have complete control over the code.
Sometimes the host framework requires certain responsibilities in its objects that we cannot move elsewhere. For example a GUI framework expects its view objects to contain data for the controls of the GUI and the data the controls display on the screen. In these cases we have to either give the testable object a reference to the Humble Object and have it manipulate the data for it or put some minimal update logic in the Humble Object and accept that it won't be covered by automated tests. The former is almost always possible and is always preferable.
To refactor to a True Humble Object, we normally do a series of Extract Method refactorings to decouple the public interface of the Humble Object from the implementation logic we plan to delegate. Then we do a Extract Class[Fowler] refactoring moving all the methods except the methods that define the public interface of the Humble Object) to the new "testable" class. We introduce a attribute (a field) to hold a reference to an instance of the new class and initialized it to an instance of the new class either as part of the constructor or using Lazy Initialization[SBPP] in each interface method.
When testing a True Humble Object (where the Humble Object delegates to a separate class) we typically use a Lazy Mock Object (see Mock Object on page X) or Test Spy to verify that the extracted class is being called correctly. Note that using the more common Active Mock Object (see Mock Object) is problematic because the assertions are done on a different thread from the Testcase Object (page X) and failures won't be detected unless we find a way to channel them back to the test thread.
To ensure that the extracted testable component is being instantiated properly, we can use an observable Object Factory (see Dependency Lookup on page X) to construct the extracted component. The test can register as a listener to verify the right method is being called on the factory. We can also use a regular factory object and replace it during the test with a Mock Object or Test Stub to be able to monitor which factory method was called.
Variation: Subclassed Humble Object
In between these two are approaches that involve clever use of subclassing to put the logic into separate classes but still allowing them to be on a single object. There are a number of different ways to do this depending on whether the Humble Object class needs to subclass a specific framework class. I won't go into a lot of detail here as it is very specific to the language and runtime environment but the basic options are having the framework-dependent class inherit the logic to be tested from a superclass or having it delegate to an abstract method implemented by a subclass.
Motivating Example (Humble Executable)
In this example we are testing some logic that runs in its own thread and processes each request as it arrives. In each test we start up the thread, send it some messages and wait long enough so that our assertions pass. Unfortunately, it takes several seconds for the thread to start up, initialize and process the first request so the test fail unless we include a two second delay after starting the thread.
public class RequestHandlerThreadTest extends TestCase { private static final int TWO_SECONDS = 3000; public void testWasInitialized_Async() throws InterruptedException { // Setup: RequestHandlerThread sut = new RequestHandlerThread(); // Exercise: sut.start(); // Verify: Thread.sleep(TWO_SECONDS); assertTrue(sut.initializedSuccessfully()); } public void testHandleOneRequest_Async() throws InterruptedException { // Setup: RequestHandlerThread sut = new RequestHandlerThread(); sut.start(); // Exercise: enqueRequest(makeSimpleRequest()); // Verify: Thread.sleep(TWO_SECONDS); assertEquals(1, sut.getNumberOfRequestsCompleted()); assertResponseEquals(makeSimpleResponse(), getResponse()); } } Example SlowAsynchronousTests embedded from java/com/xunitpatterns/dft/RequestHandlerThreadTest.java
Ideally, we would like to test the thread with each kind of transaction individually to get better Defect Localization (see Goals of Test Automation on page X) but if we did that our test suite would take many minutes to run since each test is delaying several seconds. Another problem is that the tests won't result in an error if our active object has an exception in it's own thread.
A two second delay may not seem like a big deal but consider what happens when we have a dozen such tests. It would take us almost half a minute to run these tests. Contrast this with normal tests; we can run several hundred of those each second. Testing via the executable is affecting our productivity negatively. For the record, here's the code for the executable:
public class RequestHandlerThread extends Thread { private boolean _initializationCompleted = false; private int _numberOfRequests = 0; public void run() { initializeThread(); processRequestsForever(); } public boolean initializedSuccessfully() { return _initializationCompleted; } void processRequestsForever() { Request request = nextMessage(); do { Response response = processOneRequest(request); if (response != null) { putMsgOntoOutputQueue(response); } request = nextMessage(); } while (request != null); } } Example NonHumbleExecutable embedded from java/com/xunitpatterns/dft/RequestHandlerThread.java
To avoid the distraction of the business logic I have already used an Extract Method refactoring to move the real logic into the method processOneRequest. Likewise, I'm not showing the actual initialization logic; suffice it to say that it sets the variable _initializationCompleted when it finishes successfully.
Refactoring Notes
To create a Poor Man's Humble Object, we just expose the methods to make them visible from the test. (If the logic was inline, we'd have to do an Extract Method refactorings first.) If there were any dependencies on the context, we would need to do an Introduce Parameter[JBrains] refactoring or Introduce Field[JetBrains] refactoring so that the processOneRequest method does not have to access anything from the context.
To create a true Humble Object, we can do an Extract Class refactoring on the executable to create the testable component leaving behind just the Humble Object as an empty shell. This typically involves doing the Extract Method refactorings described above to separate the logic we want to test (e.g. the initializeThread method and the processOneRequest method) from the logic that interacts with the context of the executable. We then do an Extract Class refactoring to introduce the testable component class (essentially a single Strategy[GOF] object) and move all but the public interface methods over to it. The Extract Class refactoring includes introducing a field to hold a reference to the new object and creating in instance. It also includes fixing up all the public methods to call the methods that were moved to the new testable class.
Example: Poor Man's Humble Executable
Here is the same set of tests rewritten as a Poor Man's Humble Object:
public void testWasInitialized_Sync() throws InterruptedException { // Setup: RequestHandlerThread sut = new RequestHandlerThread(); // Exercise: sut.initializeThread(); // Verify: assertTrue(sut.initializedSuccessfully()); } public void testHandleOneRequest_Sync() throws InterruptedException { // Setup: RequestHandlerThread sut = new RequestHandlerThread(); // Exercise: Response response = sut.processOneRequest(makeSimpleRequest()); // Verify: assertEquals(1, sut.getNumberOfRequestsCompleted()); assertResponseEquals(makeSimpleResponse(), response); } Example PoorMansHumbleExecTests embedded from java/com/xunitpatterns/dft/RequestHandlerThreadTest.java
I have simply made the methods initializeThread and processOneRequest public so that I could call them synchronously from the test. Note the absence of a delay in this test. This approach works fine as long as we can instantiate the executable component easily.
Example: True Humble Executible
Here is the same test refactored to use a True Humble Executible:
public class HumbleRequestHandlerThread extends Thread implements Runnable { public RequestHandler requestHandler; public HumbleRequestHandlerThread() { super(); requestHandler = new RequestHandlerImpl(); } public void run() { requestHandler.initializeThread(); processRequestsForever(); } public boolean initializedSuccessfully() { return requestHandler.initializedSuccessfully(); } public void processRequestsForever() { Request request = nextMessage(); do { Response response = requestHandler.processOneRequest(request); if (response != null) { putMsgOntoOutputQueue(response); } request = nextMessage(); } while (request != null); } Example HumbleExecutable embedded from java/com/xunitpatterns/dft/HumbleRequestHandlerThread.java
We have moved the method processOneRequest to a separate class that we can instantiate easily. Here's the same test rewritten to take advantage of the extracted component. Note the absence of a delay in this test.
public void testNotInitialized_Sync() throws InterruptedException { // Setup/Exercise: RequestHandler sut = new RequestHandlerImpl(); // Verify: assertFalse("init", sut.initializedSuccessfully()); } public void testWasInitialized_Sync() throws InterruptedException { // Setup: RequestHandler sut = new RequestHandlerImpl(); // Exercise: sut.initializeThread(); // Verify: assertTrue("init", sut.initializedSuccessfully()); } public void testHandleOneRequest_Sync() throws InterruptedException { // Setup: RequestHandler sut = new RequestHandlerImpl(); // Exercise: Response response = sut.processOneRequest( makeSimpleRequest() ); // Verify: assertEquals( 1, sut.getNumberOfRequestsDone()); assertResponseEquals( makeSimpleResponse(), response); } Example SynchronousThreadTest embedded from java/com/xunitpatterns/dft/test/HumbleRequestHandlerTest.java
Because we have introduced delegation to another object, we should probably verify that the delegation occurs properly. Here's a test that verifies that the Humble Object calls the initializeThread method and the processOneRequest method on the newly created testable component:
public void testLogicCalled_Sync() throws InterruptedException { // Setup: RequestHandlerRecordingStub mockHandler = new RequestHandlerRecordingStub(); HumbleRequestHandlerThread sut = new HumbleRequestHandlerThread(); // Mock Installation: sut.setHandler( mockHandler ); sut.start(); // Exercise: enqueRequest(makeSimpleRequest()); // Verify: Thread.sleep(TWO_SECONDS); assertTrue("init", mockHandler.initializedSuccessfully() ); assertEquals( 1, mockHandler.getNumberOfRequestsDone() ); } Example SynchronousThreadCallingTest embedded from java/com/xunitpatterns/dft/test/HumbleRequestHandlerTest.java
Note that this test does require at least a small delay to allow the thread to start up but the delay can be shorter because we have replaced the real logic component with a Test Double that responds instantly and only this one test requires the delay. We could even move this test to a separate test suite that is run less frequently (say only during the automated build process) to keep the tests we run before each check-in fast.
The other significant thing to note is that we are using a Test Spy rather than a Mock Object. This is because the assertions done by the Mock Object would be raised in a different thread from the Test Method so the Test Automation Framework (page X) (in this example JUnit) won't catch them. The test could indicate "pass" even though assertions in the Mock Object are failing. By doing the assertions in the Test Method, we avoid having to do something special to relay the exceptions thrown by the Mock Object back to the thread in which the Test Method is executing.
This last test verified that our Humble Object actually delegates to the Test Spy that we have installed. It would also be good to verify that our Humble Object actually initializes the variable holding the delegate to the appropriate class. Here's a simple way to do this:
public void testConstructor() { // Exercise: HumbleRequestHandlerThread sut = new HumbleRequestHandlerThread(); // Verify: String actualDelegateClass = sut.requestHandler.getClass().getName(); assertEquals( RequestHandlerImpl.class.getName(), actualDelegateClass); } Example StubbableAttributeInitializationTest embedded from java/com/xunitpatterns/dft/test/HumbleRequestHandlerTest.java
Note that this is just a Constructor Test (see Test Method) that verifies a specific attribute has been initialized.
Example: Humble Dialog
Many development environments let us build the user interface visually by dragging and dropping various visual objects ("widgets") onto a canvas. They let us add behavior to these visual objects by selecting one of the possible actions or events specific to that visual object and typing logic into the code window the IDE presents us with. This logic may involve invoking the application behind the user interface or it may involve modifying the state of this or other visual objects.
Visual objects are very hard to test efficiently because they are tightly coupled to the presentation framework that invokes them. The test would need to simulate that environment to provide the visual object with all the information and facilities it requires. This makes testing very complicated, so much so that many development teams don't bother testing the presentation logic at all. This leads to Production Bugs caused by untested code and Untested Requirements.
To create the Humble Dialog, we extract all the logic from the view component into a non-visual component that is testable via synchronous tests. If this component needs to update the view object's (the Humble Dialog's) state, the Humble Dialog is passed in as an argument. When testing the non-visual component, we typically replace the Humble Dialog with a Mock Object that is configured with the indirect input values and the expected behavior (indirect outputs.) In some GUI frameworks, where the Humble Dialog has to register itself with the framework for each event it wishes to see, the non-visual component can register itself instead of the Humble Dialog (as long as that doesn't introduce unmanageable dependencies on the context) and this makes the Humble Dialog even simpler because these events go directly to the non-visual component and require no delegation logic.
The following code sample is from a VB view component (.ctl) with some non-trivial logic. It is part of a custom plug-in we built for Mercury Interactive's TestDirector tool.
' Interface method, TestDirector will call this method ' to display the results.Public Sub ShowResultEx(TestSetKey As TdTestSetKey, _ TSTestKey As TdTestKey, _ ResultKey As TdResultKey) Dim RpbFiles As OcsRpbFiles Set RpbFiles = getTestResultFileNames(ResultKey) ResultsFileName = RpbFiles.ActualResultFileName ShowFileInBrowser ResultsFileName End Sub Function getTestResultFileNames(ResultKey As Variant) _ As OcsRpbFiles On Error GoTo Error Dim Attachments As Collection Dim thisTest As Run Dim RpbFiles As New OcsRpbFiles Call EnsureConnectedToTd Set Attachments = testManager.GetAllAttachmentsOfRunTest(ResultKey) Call RpbFiles.LoadFromCollection(Attachments, "RunTest") Set getTestResultFileNames = RpbFiles Exit Function Error: ' do something ...End Function Example VbViewWithLogic embedded from VB6/HumbleDialog-Not/ResultViewerPane.ctl
Ideally, we would like to test the logic but we are unable to construct the objects passed in as parameters because they don't have public constructors. Passing in objects of some other type isn't possible either because the types of the function parameters are hard-coded to be specific concrete classes.
We can do an Extract Testable Component (page X) refactoring on the executable to create the testable component leaving behind just the Humble Dialog as an empty shell. This typically involves doing several Extract Method refactorings (already done in the original example to make the refactoring easier to understand), one for each chunk of logic that we want to move. We then do an Extract Class refactoring to create our new testable component class. The Extract Class refactoring may include both Move Method[Fowler] refactorings and Move Field[Fowler] refactoring to move the logic and the data it requires from the Humble Dialog to the new testable component.
Here's the same view converted to a Humble Dialog:
' Interface method, TestDirector will call this method ' to display the results.Public Sub ShowResultEx(TestSetKey As TdTestSetKey, _ TSTestKey As TdTestKey, _ ResultKey As TdResultKey) Dim RpbFiles As OcsRpbFiles Call EnsureImplExists Set RpbFiles = Implementation.getTestResultFileNames(ResultKey) ResultsFileName = RpbFiles.ActualResultFileName ShowFileInBrowser ResultsFileName End Sub Private Sub EnsureImplExists() If Implementation Is Nothing Then Set Implementation = New OcsScriptViewerImpl End If End Sub Example VbViewAsHumbleDialog embedded from VB6/HumbleDialog/ResultViewerPane.ctl
Here's the testable component OcsScriptViewerImpl that the Humble Object calls:
' ResultViewer Implemenation: Public Function getTestResultFileNames(ResultKey As Variant) _ As OcsRpbFiles On Error GoTo Error Dim Attachments As Collection Dim thisTest As Run Dim RpbFiles As New OcsRpbFiles Call EnsureConnectedToTd Set Attachments = testManager.GetAllAttachmentsOfRunTest(ResultKey) Call RpbFiles.LoadFromCollection(Attachments, "RunTest") Set getTestResultFileNames = RpbFiles Exit Function Error: ' do something ...End Function Example VbDialogTestableComponent embedded from VB6/HumbleDialog/OcsScriptViewerImpl.cls
We could now instantiate this OcsScriptViewerImpl class easily and write VbUnit tests for it. I've omitted the tests for space reasons because they don't really show anything particularly interesting.
Example: Humble Transaction Controller
Transaction Rollback Teardown (page X) contains an example of writing tests that bypass the Humble Transaction Controller. In the interest of space, I won't repeat them here.
Further Reading
See http://www.objectmentor.com/resources/articles/TheHumbleDialogBox.pdf for Michael Feathers original write-up of Humble Dialog.
Copyright © 2003-2008 Gerard Meszaros all rights reserved